package org.restql.db.operation;

import java.io.PrintWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.restql.webapp.RestQLException;
import org.restql.webapp.ReturnType;
import org.restql.db.Utility;
import org.restql.db.resource.DatabaseResource;
import org.restql.db.resource.RequestResource;
import org.restql.webapp.output.XmlPrinter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

public class DatabaseSelect extends DatabaseGet {

	public DatabaseSelect(DatabaseResource databaseResource, RequestResource requestResource) {
		super(databaseResource, requestResource);
	}

	private static Log logger = LogFactory.getLog(DatabaseSelect.class);

	public void executeSql(PrintWriter printer, ReturnType returnType, String show) throws RestQLException {
		try {
			String constructedSQL = databaseResource.getSelectString(this.requestResource, null, null);

			// Execute SQL query
			PreparedStatement stmt =
				getConnection().prepareStatement(constructedSQL);

			logger.info("Executing: " + constructedSQL);

			ResultSet rs = stmt.executeQuery();
			Map<String, String> colTypes = getColInfo(rs);
			if (returnType == ReturnType.dtd) {
				new RuntimeException("Cannot export dtd from select method");
			} else if (returnType == ReturnType.json) {
				Map queryMap = this.requestResource.getQueryParameters();
				String callback = null; 
				for (Object key : queryMap.keySet()) {
					if (((String) key).equals("callback") ) {
						callback = ((String []) queryMap.get(key))[0];
					}
				}
				String doc = buildJSON(rs, colTypes.keySet(), callback, show);
				printer.print(doc);
			} else if (returnType == ReturnType.xml) {
				Document doc = buildDOM(rs, colTypes.keySet(), show);
				XmlPrinter.printXml(doc, printer);
			} else if (returnType == ReturnType.xsd) {
				new RuntimeException("Cannot export xsd from select method");
			}

		} catch (SQLException e) {
			logger.info(e.getMessage());
			throw new RestQLException("Error executing select query", e);
		} catch (ParserConfigurationException e) {
			throw new RestQLException("Error building dom", e);
		} catch (TransformerException e) {
			throw new RestQLException("Error printing dom", e);
		}
	}

	protected Document buildDOM(ResultSet rs, Set<String> colNames, String show) throws SQLException, ParserConfigurationException {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document doc = builder.newDocument();
		
		//2009/11/15	restql-continue	f.yang
		//Element root = doc.createElement(this.requestResource.getDbName());
		Element root = null;
		int rowLimit = 0;
		if (show != null) {
			root = null;
			rowLimit = 1;
		} else {
			root = doc.createElement("RecordSet");
			doc.appendChild(root);
		}
		int rowNo = 0;
		while (rs.next() && (rowLimit == 0 || rowNo < rowLimit)) {
			rowNo++;
			Element tableRow = doc.createElement(Utility.nameSql2Js(this.requestResource.getResourceName()));
			if (root != null) {
				root.appendChild(tableRow);
			} else {
				doc.appendChild(tableRow);
			}

			for (String colName : colNames) {
				colName = Utility.nameSql2Js(colName);
				Element fieldElement = doc.createElement(colName);
				String fieldValue = rs.getString(colName);
				if (fieldValue != null) {
					Text t = doc.createTextNode(fieldValue);
					fieldElement.appendChild(t);
					tableRow.appendChild(fieldElement);
				}
			}
		}
		return doc;
	}

	protected String buildJSON(ResultSet rs, Set<String> colNames, String callback, String show) throws SQLException, ParserConfigurationException {

		StringBuffer doc = new StringBuffer();
		//Stack<String> todo = new Stack<String>();
		String[] callbackNode = {callback + "(", ");"};
		String[] emptyNode = {"", ""};
		if (callback == null) {
			callbackNode = emptyNode;
		}
		doc.append(callbackNode [0]);
		String[] root = {"[", "]"};
		int rowLimit = 0;
		if (show != null) {
			root = emptyNode;
			rowLimit = 1;
		}
		
		doc.append(root[0]);
		int rowNo = 0;
		while (rs.next() && (rowLimit == 0 || rowNo < rowLimit)) {
			doc.append((rowNo++ != 0)? "\n," : " ");
			String[] tableRow = {"{", "}"};
			doc.append(tableRow[0]);
			
			int fieldNo = 0;
			for (String colName : colNames) {
				colName = Utility.nameSql2Js(colName);
				String fieldElement = 
					((fieldNo++ != 0)? "," : " ") 
					+ "\"" + colName + "\" : " 
					+ ((rs.getString(colName) == null) ? "null" : "\"" + rs.getString(colName).replaceAll("\"", "\\\"") + "\"");
				doc.append(fieldElement);
			}

			doc.append(tableRow[1]);
		}
		doc.append(root[1]);//root
		doc.append(callbackNode[1]);//callback
		return doc.toString();
	}

}
